/*
** Classification algorithms for svms
*/

#include "crf_classifier.h"
#include "util.h"

#include <strings.h>
#include <sstream>
#include <float.h>
#include <fstream>
#include <iomanip>

bool DUMP=false; // print out viterbi alignment table

/*
** A classifier function for the a gaussian process algorithm
*/
CRFClassifier::CRFClassifier(boost::shared_array<double> alpha, GPParameterPtr param)
: m_alpha(alpha)
{
}

std::vector<unsigned int> CRFClassifier::classify(EventPtr v)
{
//    return beam(v);
//      if (true)
          return viterbi(v,DUMP);
}

std::vector<unsigned int> CRFClassifier::beam(const std::vector<SparseArrayPtr> &v)
{
    std::vector<unsigned int> result;
//  SparseArray *instance;
    for (unsigned int i=0; i < v.size(); ++i)
    {
//        instance = SequenceDataSet::add_context(v[i], result, _param->k,1); 
//        result.push_back(distribution(instance)[0].second);
    }
    return result;
}

std::vector<unsigned int> 
CRFClassifier::viterbi(EventPtr v, bool print)
{
    std::vector<unsigned int> result;
  
//    // assume every labelled clique is the same size
//    unsigned int width = v->cliques[0]->labels().first;
//    unsigned int num_cliques = v->cliques.size();
//
//    // viterbi data structures
//    double V[num_cliques+1][width];
//    unsigned int B[num_cliques+1][width];
//    std::vector<std::pair<double,int> >::iterator probs_it;
//
//    /////////////////////////////////////////////////////////////////
//    // initialisation step
//    // start with a uniform distribution over initial labels
//    for (unsigned int i=0; i < width; ++i)
//    {
//        V[0][i] = 0; 
//        B[0][i] = 0;
//    }
//
//    if (print)
//    {
//        for (unsigned int j=0; j < width; ++j)
//              std::cout << " " << std::setprecision(2) << std::setw(6) << V[0][j];
//        std::cout << std::endl;
//        for (unsigned int j=0; j < width; ++j)
//              std::cout << " " << std::setprecision(2) << std::setw(6) << B[0][j];
//        std::cout << std::endl; std::cout << std::endl;
//    }
//    /////////////////////////////////////////////////////////////////
//
//    /////////////////////////////////////////////////////////////////
//    // main max product(viterbi) loop
//    int null_index=0;
//    double transition_prob=0.0;
//    for (unsigned int i=1; i <= num_cliques; ++i)
//    {
//        // init column
//        for (unsigned int k=0; k < width; ++k)
//            V[i][k] = -FLT_MAX;
//
//        // fill column
//        Clique *markov_clique = static_cast<Clique*>(v->cliques[i-1].get());
////        m_data->encode_markov(*markov_clique, null_index);
//        std::vector<std::vector<std::pair<double,int> > > probs 
//            = distribution(*markov_clique);
//
//        for (unsigned int k=0; k < width; ++k)
//        {
//            probs_it = probs[k].begin();
//            for (; probs_it != probs[k].end(); ++probs_it)
//            {
//                transition_prob = log(probs_it->first) + V[i-1][k];
//                if (transition_prob > V[i][probs_it->second])
//                {
//                    V[i][probs_it->second] = transition_prob;
//                    B[i][probs_it->second] = k;
//                }
//            }
//        }
//
//        // special treatment for null transitions
//        if (B[i][width-1] != (width-1)) null_index = B[i][width-1];
//        else null_index++;
//
//        if (print)
//        {
//            for (unsigned int j=0; j < width; ++j)
//                  std::cout << " " << std::setprecision(2) << std::setw(6) << V[i][j];
//            std::cout << std::endl;
//            for (unsigned int j=0; j < width; ++j)
//                  std::cout << " " << std::setprecision(2) << std::setw(6) << B[i][j];
//            std::cout << " " << std::setprecision(2) << std::setw(6) << null_index;
//            std::cout << std::endl;
//            std::cout << std::endl;
//        }
//    }
//
//    /////////////////////////////////////////////////////////////////
//    // resolve max labelling 
//    unsigned int best_k=0;
//    double best_p = -FLT_MAX;
//    for (unsigned int k=0; k < width; ++k)
//        if (V[num_cliques][k] > best_p)
//        {
//            best_k = k;
//            best_p = V[num_cliques][k];
//        }
//
//    result.push_back(best_k);
//    for (unsigned int i=num_cliques; i > 0; --i)
//        result.push_back(B[i][result.back()]);
//
//    std::reverse(result.begin(),result.end());
//
//    if (print)
//    {
//        for (unsigned int j=0; j < result.size(); ++j)
//              std::cout << " " << std::setprecision(2) << std::setw(6) << j;
//        std::cout << std::endl;
//        for (unsigned int j=0; j < result.size(); ++j)
//              std::cout << " " << std::setprecision(2) << std::setw(6) << result[j];
//        std::cout << std::endl;
//        std::cout << std::endl;
//        std::cout << std::endl;
//    }

    return result;
}

std::vector<std::vector<std::pair<double,int> > > 
CRFClassifier::distribution(const Clique &v)
{
    std::vector<std::vector<std::pair<double,int> > > result;
    //double null_multiplier = 1.0;
    double Z=0;
    double max_product = -FLT_MAX;

    for (int first_label=0; first_label < v.labels().first; ++first_label)
    {
        // process vectors first as we can do them quickly
        std::vector<std::pair<double,int> > class_probs;
        for(int second_label=0; second_label < v.labels().second; ++second_label)
        {
            double prob 
                = G->evaluate(v.labelling(first_label,second_label).get());
            class_probs.push_back(std::pair<double,int>(prob,second_label));
            if(prob > max_product) max_product = prob;
        }

        //////////////////////////////////////////////////////////  
        // !!ALIGNMENT SPECIFIC!! scale the last value in the vector
//      class_probs[class_probs.size()-1] *= null_multiplier;
        //////////////////////////////////////////////////////////  

        result.push_back(class_probs);
    }

    // calculate Z
    for (int first_label=0; first_label < v.labels().first; ++first_label)
    {
        // use log sum to calculate Z without overflow
        for(int second_label=0; second_label < v.labels().second; ++second_label)
            Z += exp(result[first_label][second_label].first - max_product);
    }
    Z = log(Z) + max_product;

    // normalise
    for (int first_label=0; first_label < v.labels().first; ++first_label)
    {
        int counter=0;
        for(int second_label=0; second_label < v.labels().second; ++second_label)
        {
            double prob = exp(result[first_label][second_label].first - Z);
            if(!(prob <= 1.0 && prob >= 0.0)) 
                assert(false);
            result[first_label][second_label].first = prob;
            counter++;
        }
        
//      std::sort(labelling.begin(), labelling.end());
//      std::reverse(labelling.begin(), labelling.end());
//      result.push_back(labelling);
    }

    return result;
}


EvaluationPtr CRFClassifier::evaluate(EventsPtr data, GPParameterPtr param)
{
    EvaluationPtr result(new Evaluation);

//    std::string output_file = "predicted_classes";
//    std::ofstream outFile;
//    outFile.open(output_file.c_str());
//
//    std::string str;
//    pp("",true);
//    int counter=0;
//    for (int e=0; e < data->events(); ++e)
//    {
////      if (e==34 || e==214) DUMP=true;
////      if (e==0) DUMP=true;
////      else DUMP=false;
//        if ((counter % 1) == 0)
//        {
//            std::stringstream s;
//            s << "  " << counter << " instances classified";
//            pp(s.str());
//        }
//        
//        Event *event = data->event(e).get();
//        const std::vector<unsigned int> &labels = classify(event);
//
//        
//        // handle label of the first node by itself
//        unsigned int gold = event->cliques[0]->gold().first;
//        if(gold == labels[0]) result->tp++;
//        else result->fp++;
//        if (output_file != "") outFile << labels[0] << std::endl;
//
//        // handle the rest of the nodes
//        for (unsigned int c=0; c < event->cliques.size(); ++c)
//        {
//            gold = event->cliques[c]->gold().second;
//            if(gold == labels[c+1]) result->tp++;
//            else result->fp++;
//            if (output_file != "") outFile << labels[c+1] << std::endl;
//        }
//        counter++;
//    }
//    pp("\n",true);
//
//    if (output_file != "") outFile.close();
//
    return result;
}
